/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_subview.cpp - subview rendering
//


#include "r_local.h"


/*
 ==============================================================================

 SUBVIEW SETUP

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void R_PlaneForSurface (dsType_t type, dsGeometry_t *geometry, entity_t *entity, glqPlane &plane){

	surface_t			*surface;
	aliasMdlSurface_t	*aliasMdlSurface;
	glqVec3				normal;

	// Get the plane
	switch (type){
	case DS_SURFACE:
		surface = (surface_t *)geometry;

		// FIXME: do this like Q2e (R_AddSubviewSurface)

		break;
	case DS_MODEL:
		aliasMdlSurface = (aliasMdlSurface_t *)geometry;

		// FIXME: do this like Q2e (R_AddSubviewSurface)

		break;
	default:
		Com_Error(false, "R_PlaneForSurface: bad surface type (%i)", type);
	}

	// Transform the plane into world space if needed
	if (entity != rg.scene->worldEntity){
		normal = entity->e.axis * plane.Normal();

		plane.SetNormal(normal);
		plane.SetDist(plane.Dist() + entity->e.origin.Dot(normal));
		plane.SetSignBits();
	}
}

/*
 ==================
 
 Checks the surface for visibility on a per-triangle basis for cases when it is
 going to be very expensive to draw (subviews).

 If not culled, also returns the bounding box of the surface in normalized
 device coordinates, so it can be used to crop the scissor region.
 ==================
*/
static bool R_PreciseCullSurface (dsType_t type, dsGeometry_t *geometry, entity_t *entity, material_t *material, glqBounds &ndcBounds){

	return false;
}

/*
 ==================
 R_MirrorViewForSurface
 ==================
*/
static bool R_MirrorViewForSurface (dsType_t type, dsGeometry_t *geometry, entity_t *entity, material_t *material){

	glqBounds	bounds;
	glqPlane	plane;
	glqMat3		axis;
	glqAngles	angles;
	float		dist;

	// Don't allow multiple subviews in a single frame
	if (rg.subviewParms.viewType != VIEW_NONE){
		if (rg.subviewParms.viewType != VIEW_MIRROR)
			return false;

		if (rg.subviewParms.entity != entity)
			return false;
		if (rg.subviewParms.material != material)
			return false;

		// Make sure the surface planes are the same
		R_PlaneForSurface(type, geometry, entity, plane);

		if (rg.subviewParms.plane != plane)
			return false;

		// Cull the surface
		if (R_PreciseCullSurface(type, geometry, entity, material, bounds))
			return false;

		// Add to bounds
		rg.subviewParms.ndcBounds += bounds;

		return true;
	}

	// Get the world space surface plane
	R_PlaneForSurface(type, geometry, entity, plane);

	// Check if the view origin is at the wrong side of the plane
	if (material->cullType == CT_FRONT_SIDED){
		if (plane.PointOnSide(rg.renderView.origin) != PLANESIDE_FRONT)
			return false;
	}
	else if (material->cullType == CT_BACK_SIDED){
		if (plane.PointOnSide(rg.renderView.origin) != PLANESIDE_BACK)
			return false;
	}

	// Cull the surface
	if (R_PreciseCullSurface(type, geometry, entity, material, bounds))
		return false;

	// Set up the subview
	rg.subviewParms.viewType = VIEW_MIRROR;

	rg.subviewParms.entity = entity;
	rg.subviewParms.material = material;

	rg.subviewParms.ndcBounds = bounds;

	rg.subviewParms.plane = plane;

	// Compute mirror view origin
	dist = 2.0f * plane.Distance(rg.renderView.origin);

	rg.subviewParms.origin = rg.renderView.origin - plane.Normal() * dist;

	// Compute mirror view axis
	axis[0].Reflect(rg.renderView.axis[0], plane.Normal());
	axis[1].Reflect(rg.renderView.axis[1], plane.Normal());
	axis[2].Reflect(rg.renderView.axis[2], plane.Normal());

	axis[0].Normalize();
	axis[1].Normalize();
	axis[2].Normalize();

	angles = axis.ToAngles();
	angles.roll = -angles.roll;

	rg.subviewParms.axis = angles.ToMat3();

	// Set the FOV
	rg.subviewParms.fovX = rg.viewParms.fovX;
	rg.subviewParms.fovY = rg.viewParms.fovY;
	rg.subviewParms.fovScale = rg.viewParms.fovScale;

	// Set the PVS origin
	rg.subviewParms.pvsOrigin = rg.renderView.origin;

	// Set the clip plane
	rg.subviewParms.clipPlane = plane;

	return true;
}

/*
 ==================
 R_RemoteViewForSurface
 ==================
*/
static bool R_RemoteViewForSurface (dsType_t type, dsGeometry_t *geometry, entity_t *entity, material_t *material){

	glqBounds	bounds;
	glqPlane	plane;
	glqMat3		viewAxis;
	glqVec3		viewOrigin;

	if (!entity->e.hasSubview)
		Com_Error(false, "R_RemoteViewForSurface: invalid subview");

	// Don't allow multiple subviews in a single frame
	if (rg.subviewParms.viewType != VIEW_NONE){
		if (rg.subviewParms.viewType != VIEW_REMOTE)
			return false;

		if (rg.subviewParms.entity != entity)
			return false;
		if (rg.subviewParms.material != material)
			return false;

		// Make sure the surface planes are the same
		R_PlaneForSurface(type, geometry, entity, plane);

		if (rg.subviewParms.plane != plane)
			return false;

		// Cull the surface
		if (R_PreciseCullSurface(type, geometry, entity, material, bounds))
			return false;

		// Add to bounds
		rg.subviewParms.ndcBounds += bounds;

		return true;
	}

	// Get the world space surface plane
	R_PlaneForSurface(type, geometry, entity, plane);

	// Check if the view origin is at the wrong side of the plane
	if (material->cullType == CT_FRONT_SIDED){
		if (plane.PointOnSide(rg.renderView.origin) != PLANESIDE_FRONT)
			return false;
	}
	else if (material->cullType == CT_BACK_SIDED){
		if (plane.PointOnSide(rg.renderView.origin) != PLANESIDE_BACK)
			return false;
	}

	// Cull the surface
	if (R_PreciseCullSurface(type, geometry, entity, material, bounds))
		return false;

	// Get the view origin and view axis
	viewOrigin = entity->e.subviewOrigin;
	viewAxis = entity->e.subviewAngles.ToMat3();

	// Set up the subview
	rg.subviewParms.viewType = VIEW_REMOTE;

	rg.subviewParms.entity = entity;
	rg.subviewParms.material = material;

	rg.subviewParms.ndcBounds = bounds;

	rg.subviewParms.plane = plane;

	// Compute remote view origin
	rg.subviewParms.origin = viewOrigin;

	// Compute remote view axis
	rg.subviewParms.axis = viewAxis;

	// Set the FOV
	rg.subviewParms.fovX = entity->e.subviewFovX;
	rg.subviewParms.fovY = entity->e.subviewFovY;
	rg.subviewParms.fovScale = entity->e.subviewFovX * (1.0f / 90.0f);

	// Set the PVS origin
	rg.subviewParms.pvsOrigin = viewOrigin;

	// Set the clip plane
	rg.subviewParms.clipPlane.Set(viewAxis[0], viewOrigin.Dot(viewAxis[0]));

	return true;
}

/*
 ==================
 R_AddSubviewSurface
 ==================
*/
bool R_AddSubviewSurface (dsType_t type, dsGeometry_t *geometry, entity_t *entity, material_t *material){

	if (r_skipSubviews->integerValue)
		return true;

	if (rg.viewParms.viewType != VIEW_MAIN)
		return false;		// Avoid recursion

	switch (material->subviewType){
	case ST_MIRROR:
		if (!R_MirrorViewForSurface(type, geometry, entity, material))
			return false;

		break;
	case ST_REMOTE:
		if (!R_RemoteViewForSurface(type, geometry, entity, material))
			return false;

		break;
	default:
		Com_Error(false, "R_AddSubviewSurface: bad subview type (%i)", material->subviewType);
	}

	return true;
}


/*
 ==============================================================================

 SUBVIEW RENDERING

 ==============================================================================
*/


/*
 ==================
 R_RenderSubview
 ==================
*/
bool R_RenderSubview (){

	material_t		*material;
	renderView_t	oldRenderView;
	viewParms_t		oldViewParms;

	if (rg.viewParms.viewType != VIEW_MAIN)
		return false;		// Avoid recursion

	if (rg.subviewParms.viewType == VIEW_NONE)
		return false;		// No subview available

	material = rg.subviewParms.material;

	// Backup main view
	Mem_Copy(&oldRenderView, &rg.renderView, sizeof(renderView_t));
	Mem_Copy(&oldViewParms, &rg.viewParms, sizeof(viewParms_t));

	// Set up the view
	rg.renderView.origin = rg.subviewParms.origin;
	rg.renderView.axis = rg.subviewParms.axis;

	rg.renderView.fovX = rg.subviewParms.fovX;
	rg.renderView.fovY = rg.subviewParms.fovY;
	rg.renderView.fovScale = rg.subviewParms.fovScale;

	// Render the subview to a texture
	R_CropRenderSize(material->subviewWidth, material->subviewHeight, false);
	R_RenderView(rg.viewParms.primaryView, rg.subviewParms.viewType);
	R_CaptureRenderToTexture(material->subviewTexture->name);
	R_UnCropRenderSize();

	// Restore main view
	Mem_Copy(&rg.renderView, &oldRenderView, sizeof(renderView_t));
	Mem_Copy(&rg.viewParms, &oldViewParms, sizeof(viewParms_t));

	// Clear subview
	Mem_Fill(&rg.subviewParms, 0, sizeof(subviewParms_t));

	return true;
}